﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.SqlTypes;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Security.Permissions;
using System.Threading;
using System.Xml.Linq;
using Microsoft.Contracts;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Nvigorate.Relational.State;
using NI = Ninject.Core.Infrastructure;
using Nvigorate.Common;
using Nvigorate.Common.Reflection;
using Nvigorate.Relational.Mapping;
using Nvigorate.Relational.Mapping.Fluent;
using Nvigorate.TestObjects;
using Nvigorate.Extensions;

namespace Nvigorate.Test.Common
{
    /// <summary>
    /// Summary description for ReflectorTests
    /// </summary>
    [TestClass]
    public class ReflectorTests
    {
        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext { get; set; }

        public ReflectorTests()
        {
            (myTest as StateTracking).Deserializing = true;
        }

        #region Additional test attributes

        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        // [TestInitialize()]
        // public void MyTestInitialize() { }
        //
        // Use TestCleanup to run code after each test has run
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //

        #endregion

        #region Nested type definitions for unit tests

        private class MultipleConstructorClass
        {
            [SecurityPermission(SecurityAction.Assert)]
            public MultipleConstructorClass()
            {
            }

            [SecurityPermission(SecurityAction.Assert)]
            [Bindable(false)]
            public MultipleConstructorClass(string somethingImportant)
            {
            }

            [SecurityPermission(SecurityAction.Assert)]
            public MultipleConstructorClass(string somethingImportant, object weaklyTypedButImportantData)
            {
            }
        }

        [Serializable]
        private class JustANormalClass
        {
            [NonSerialized] private string _field;

            [Browsable(false)]
            [Bindable(true)]
            public string Field
            {
                get { return _field; }
            }
        }

        private class PropertyOverloadClass
        {
            public virtual string ImportantData
            {
                get { return "important!"; }
            }
        }

        private class DerivedPropertyOverloadClass : PropertyOverloadClass
        {
            public override string ImportantData
            {
                get { return "more importanter!"; }
            }
        }

        private class CaseSensitivityClass
        {
            private int _data;
            private int _Data;
        }

        [SourceAssignment("blah")]
        private interface ISomeInterface
        {
            string ImportantData { get; }
        }

        private class SomeClass : ISomeInterface
        {
            #region ISomeInterface Members

            public string ImportantData
            {
                get { return "The Data"; }
            }

            #endregion
        }

        public class AutoPropertyClass
        {
            private string _normal = "";
            public string AutoProperty { get; private set; }
            public string NormalProperty
            {
                get { return _normal; }
                set { _normal = value; }
            }

            public AutoPropertyClass(string value)
            {
                AutoProperty = value;
                NormalProperty = value;
            }
        }

        #endregion

        [TestMethod]
        public void DetermineAutoProperty()
        {
            Type type = typeof (AutoPropertyClass);

            var instance = new AutoPropertyClass("test");
            Reflector.Write(instance, "AutoProperty", "notTest");

            Assert.AreNotEqual(instance.AutoProperty, instance.NormalProperty);
        }

        [TestMethod]
        public void GetTypeTest()
        {
            Type t = Reflector.GetType("Nvigorate.TestObjects.Person");
            Assert.AreEqual(typeof (Person), t);
        }

        [TestMethod]
        public void GetElementTypeTest()
        {
            var names = new string[1];
            var ints = new List<int>();

            Assert.AreEqual(typeof (string), Reflector.GetCollectionElementType(names));
            Assert.AreEqual(typeof (int), Reflector.GetCollectionElementType(ints));
        }

        [TestMethod]
        public void GetConstructorInfo()
        {
            // 3 constructors are expected
            List<MemberInfo> constructorMembers = Reflector.GetMemberInfo(typeof (MultipleConstructorClass), ".ctor",
                                                                          MemberTypes.Constructor, false);
            Assert.AreEqual(3, constructorMembers.Count);
        }

        [TestMethod]
        public void GetFieldInfo()
        {
            Assert.AreEqual(1, Reflector.GetFields(typeof (JustANormalClass)).Count);

            Assert.IsNotNull(Reflector.GetFieldInfo(typeof (JustANormalClass), "_field", false));
            Assert.IsNotNull(Reflector.GetFieldInfo(typeof (JustANormalClass), "_field", true));

            Assert.AreEqual(1,
                            Reflector.GetFieldAttributes<NonSerializedAttribute>(typeof (JustANormalClass), "_field").
                                Count());
        }

        [TestMethod]
        public void GetPropertyInfo()
        {
            Assert.AreEqual(1, Reflector.GetProperties(typeof (JustANormalClass)).Count);

            Assert.IsNotNull(Reflector.GetPropertyInfo(typeof (JustANormalClass), "Field", false));
            Assert.IsNotNull(Reflector.GetPropertyInfo(typeof (JustANormalClass), "Field", true));

            Assert.AreEqual(1,
                            Reflector.GetPropertyAttributes<BrowsableAttribute>(typeof (JustANormalClass), "Field").
                                Count());
            Assert.AreEqual(1, Reflector.GetPropertyAttributes<BrowsableAttribute>(typeof (JustANormalClass)).Count());
            Assert.AreEqual(2, Reflector.GetPropertyAttributes<Attribute>(typeof (JustANormalClass), "Field").Count());
        }

        [TestMethod]
        public void GetOverloadedPropertyTest()
        {
            Assert.AreEqual(1,
                            Reflector.GetMemberInfo(typeof (DerivedPropertyOverloadClass), "ImportantData",
                                                    MemberTypes.Property, true).Count);
        }

        [TestMethod]
        public void GetClassInfo()
        {
            Assert.AreEqual(1, Reflector.GetClassAttributes<SerializableAttribute>(typeof (JustANormalClass)).Count);

            // TODO:  Figure out what the expected behavior of this line is because it is failing.
            // Assert.AreEqual(1, Reflector.GetClassAttributes<SourceAssignment>(typeof (SomeClass)).Count);
        }

        [TestMethod]
        public void TestListCreation()
        {
            List<Employee> employees = new List<Employee>()
                                           {
                                               new Employee(), new Employee(), new Employee()
                                           };

            object value = employees.Select(e => e).Cast<object>();

            var targetType = employees.GetType();
            Assert.IsTrue(Reflector.HasBaseTypeOf(targetType, typeof(List<>)));
            Assert.IsTrue(Reflector.ImplementsInterface(targetType, typeof(IEnumerable<>)));

            var genericArguments = targetType.GetGenericArguments();
            var type = targetType.GetGenericTypeDefinition().MakeGenericType(genericArguments);
            var enumerable = value as IEnumerable;
            var test = Activator.CreateInstance(type, MrCastastic.CastEnumerable(genericArguments.First(), enumerable));

            Assert.IsTrue(test.GetType().Equals(targetType));
        }

        [TestMethod]
        public void TestListCreationPerformance_100000()
        {
            List<Employee> employees = new List<Employee>()
                                           {
                                               new Employee(), new Employee(), new Employee()
                                           };

            object value = employees.Select(e => e).Cast<object>();

            for (int i = 0; i < 100000; i++ )
            {
                var targetType = employees.GetType();
                var genericArguments = targetType.GetGenericArguments();
                var enumerable = value as IEnumerable;
                var test = Activator.CreateInstance(targetType, MrCastastic.CastEnumerable(genericArguments.First(), enumerable));
            }
        }

        [TestMethod]
        public void TestOldListCreationPerformance_100000()
        {
            List<Employee> employees = new List<Employee>()
                                           {
                                               new Employee(), new Employee(), new Employee()
                                           };

            object value = employees.Select(e => e).Cast<object>();

            for (int i = 0; i < 100000; i++)
            {
                var targetType = employees.GetType();
                var genericArguments = targetType.GetGenericArguments();
                var enumerable = value as IEnumerable;
                var expectedElementType = targetType.GetGenericArguments().First();
                var test = TypeCaster.NewInstance<List<Employee>>(MrCastastic.CastEnumerable(expectedElementType, enumerable));
            }
        }


        [TestMethod]
        public void TestDeepClone()
        {
            var original = new Person
                               {
                                   FirstName = "People",
                                   DateOfBirth = DateTime.Now,
                                   LastName = "Person"
                               };

            var newInstance = Reflector.Clone(original);

            Reflector.PublicOnly = false;
            Reflector.SearchHierarchy = true;

            foreach (var field in new [] {"FirstName", "LastName", "DateOfBirth"})
            {
                Assert.AreEqual(
                    Reflector.Read(original, field),
                    Reflector.Read(newInstance, field)
                    );
            }
        }

        [TestMethod]
        public void TestDeepClone2()
        {
            var original = new Employee
                               {
                                   FirstName = "People",
                                   DateOfBirth = DateTime.Now,
                                   LastName = "Person"
                               };

            var newInstance = Reflector.Clone(original);

            Reflector.SearchHierarchy = true;
            Assert.IsTrue(Reflector.AreEqualValue(original, newInstance));
        }

        [TestMethod]
        public void TestStringClone()
        {
            string test = "A";

            string newString = (string) test.Clone();

            Assert.AreEqual(test, newString);

            newString = "B";

            Assert.AreNotEqual(test, newString);
        }

        [TestMethod]
        public void TestArrays()
        {
            test instance1 = new test();

            instance1.stringArray[0] = "z";
            instance1.stringamajig = "stuff";

            test instance2 = Reflector.Clone(instance1);
            Assert.AreEqual(instance1.stringArray[0], instance2.stringArray[0]);
            Assert.AreEqual(instance1.stringamajig, instance2.stringamajig);
        }

        [TestMethod]
        public void TestHasBaseTypeOfGeneric()
        {
            var employeeMap = new EmployeeMap();
            Assert.IsTrue(Reflector.HasBaseTypeOf(employeeMap.GetType(), typeof(FluentMap<>)));
        }

        [TestMethod]
        public void TestXDocumentClone()
        {
            XDocument doc = XDocument.Parse(_testRDLXml);
            XDocument newDoc = Reflector.Clone(doc);

            
            Assert.IsTrue(XNode.DeepEquals(doc, newDoc));
        }

        protected void CheckFieldsForEquality<T>(T instance1, T instance2)
        {
            Reflector.SearchHierarchy = true;
            foreach (var field in Reflector.GetFields(instance1.GetType()))
            {
                Assert.AreEqual(
                    Reflector.Read(instance1, field),
                    Reflector.Read(instance2, field)
                    );
            }
        }

        protected void CheckFieldsForInequality<T>(T instance1, T instance2)
        {
            Reflector.SearchHierarchy = true;
            foreach (var field in Reflector.GetFields(instance1.GetType()))
            {
                Assert.AreNotEqual(
                    Reflector.Read(instance1, field),
                    Reflector.Read(instance2, field)
                    );
            }
        }

        private string _testRDLXml =
            @"
<ValueMap>
    <OwningElement>Report</OwningElement>
    <Value>PageWidth</Value>
    <TargetElements>
        <TargetElement>InteractiveWidth</TargetElement>
        <TargetElement>PageWidth</TargetElement>
        <TargetElement>Width</TargetElement>
    </TargetElements>
</ValueMap>
";

        [TestMethod]
        public void TestStaticMemberAccess()
        {
            var myTest = new test();

            Assert.AreEqual("", Reflector.Read(myTest, "_field"));

            Reflector.Write(myTest, "_field", "test");

            Assert.AreEqual("test", Reflector.Read(myTest, "_field"));
            Assert.AreEqual("test", test.Field);
            Assert.AreEqual("test", Reflector.Read(myTest, "Field"));
        }

        [TestMethod]
        public void TestDateTimeConversion()
        {
            test myTest = new test();
            Reflector.Write(myTest, "date", "");
            Assert.IsFalse(myTest.date == DateTime.MinValue);
            Assert.IsTrue(myTest.date == SqlDateTime.MinValue.Value);
        }

        [TestMethod]
        public void TestGetInheritenceChain()
        {
            var types = Reflector.GetInheritenceChain(typeof(G)).Select(t => t.Name).ToList();
            Assert.AreEqual(6, types.Count);
            var list = new[] { "A", "B", "C", "D", "E", "F" }.ToList();
            list.Reverse();
            Assert.IsTrue(list.SequenceEqual(types));
        }

        #region Read/Write/Call tests

        [TestMethod]
        public void TestReadWriteTest()
        {
            var test = new Target();

            Reflector.PublicOnly = false;
            Reflector.SearchHierarchy = true;

            //test values
            string stringValue = "test";
            int intValue = 10;
            decimal decimalValue = 15.3830m;
            float floatValue = .193813f;
            DateTime dateTimeValue = DateTime.Parse("10/01/2008");
            bool boolValue = true;
            Guid guidValue = Guid.NewGuid();

            //write test values to GrandparentTarget
            Reflector.Write(test, "_level3InternalInteger", intValue);
            Reflector.Write(test, "_level3InternalString", stringValue);
            Reflector.Write(test, "_level3PrivateDecimal", decimalValue);
            Reflector.Write(test, "_level3PrivateFloat", floatValue);
            Reflector.Write(test, "_level3ProtectedBoolean", boolValue);
            Reflector.Write(test, "_level3ProtectedDateTime", dateTimeValue);

            //write test values to ParentTarget
            Reflector.Write(test, "_level2InternalInteger", intValue);
            Reflector.Write(test, "_level2InternalString", stringValue);
            Reflector.Write(test, "_level2PrivateDecimal", decimalValue);
            Reflector.Write(test, "_level2PrivateFloat", floatValue);
            Reflector.Write(test, "_level2ProtectedBoolean", boolValue);
            Reflector.Write(test, "_level2ProtectedDateTime", dateTimeValue);

            //write test values to Target
            Reflector.Write(test, "_levelInternalInteger", intValue);
            Reflector.Write(test, "_levelInternalString", stringValue);
            Reflector.Write(test, "_levelPrivateDecimal", decimalValue);
            Reflector.Write(test, "_levelPrivateFloat", floatValue);
            Reflector.Write(test, "_levelProtectedBoolean", boolValue);
            Reflector.Write(test, "_levelProtectedDateTime", dateTimeValue);

            //check values
            Assert.AreEqual(stringValue, Reflector.Read(test, "_level3InternalString"));
            Assert.AreEqual(intValue, Reflector.Read(test, "_level3InternalInteger"));
            Assert.AreEqual(decimalValue, Reflector.Read(test, "_level3PrivateDecimal"));
            Assert.AreEqual(floatValue, Reflector.Read(test, "_level3PrivateFloat"));
            Assert.AreEqual(boolValue, Reflector.Read(test, "_level3ProtectedBoolean"));
            Assert.AreEqual(dateTimeValue, Reflector.Read(test, "_level3ProtectedDateTime"));

            Assert.AreEqual(stringValue, Reflector.Read(test, "_level2InternalString"));
            Assert.AreEqual(intValue, Reflector.Read(test, "_level2InternalInteger"));
            Assert.AreEqual(decimalValue, Reflector.Read(test, "_level2PrivateDecimal"));
            Assert.AreEqual(floatValue, Reflector.Read(test, "_level2PrivateFloat"));
            Assert.AreEqual(boolValue, Reflector.Read(test, "_level2ProtectedBoolean"));
            Assert.AreEqual(dateTimeValue, Reflector.Read(test, "_level2ProtectedDateTime"));

            Assert.AreEqual(stringValue, Reflector.Read(test, "_levelInternalString"));
            Assert.AreEqual(intValue, Reflector.Read(test, "_levelInternalInteger"));
            Assert.AreEqual(decimalValue, Reflector.Read(test, "_levelPrivateDecimal"));
            Assert.AreEqual(floatValue, Reflector.Read(test, "_levelPrivateFloat"));
            Assert.AreEqual(boolValue, Reflector.Read(test, "_levelProtectedBoolean"));
            Assert.AreEqual(dateTimeValue, Reflector.Read(test, "_levelProtectedDateTime"));

        }

        [TestMethod]
        public void TestMethodCalls()
        {
            Reflector.PublicOnly = false;
            Reflector.SearchHierarchy = true;

            var test = new Target();
            var type = typeof (Target);

            //test values
            string stringValue = "test";
            int intValue = 10;
            decimal decimalValue = 15.3830m;
            float floatValue = .193813f;
            DateTime dateTimeValue = DateTime.Parse("10/01/2008");
            bool boolValue = true;
            Guid guidValue = Guid.NewGuid();

            // Instance calls


            Assert.AreEqual(guidValue, Reflector.CallMethod(test, "Level3GenericInternalCall", new object[] { guidValue }, guidValue.GetType()));
            Assert.AreEqual(floatValue, Reflector.CallMethod(test, "Level3GenericPrivateCall", new object[] {floatValue}, floatValue.GetType()));
            Assert.AreEqual(dateTimeValue, Reflector.CallMethod(test, "Level3GenericProtectedCall", new object[] {dateTimeValue}, dateTimeValue.GetType()));
            
            
            Assert.AreEqual("call 1", Reflector.CallMethod(test, "Level3NonGenericInternalCall", stringValue, intValue, decimalValue));
            Assert.AreEqual("call 2", Reflector.CallMethod(test, "Level3NonGenericInternalCall", boolValue, intValue, decimalValue));

            Assert.AreEqual("call 1", Reflector.CallMethod(test, "Level3NonGenericProtectedCall", stringValue, intValue, decimalValue));
            Assert.AreEqual("call 2", Reflector.CallMethod(test, "Level3NonGenericProtectedCall", boolValue, intValue, decimalValue));

            Assert.AreEqual("call 1", Reflector.CallMethod(test, "Level3NonGenericPrivateCall", stringValue, intValue, decimalValue));
            Assert.AreEqual("call 2", Reflector.CallMethod(test, "Level3NonGenericPrivateCall", boolValue, intValue, decimalValue));

            // Static Calls
        }

        #endregion

        [TestMethod]
        public void TestLambdaPathCreation()
        {
            var path = Assign<Employee, long>(e => e.Id);
            Assert.AreEqual("Id", path);

            path = Assign<Employee, long>(e => e.Department.Id);
            Assert.AreEqual("Department.Id", path);

            path = Assign<Employee, long>(e => e.Department.Manager.Id);
            Assert.AreEqual("Department.Manager.Id", path);
        }

        [TestMethod]
        public void TestNestedRead()
        {
            var employee = new Employee()
                               {
                                   FirstName = "Billy",
                                   Department = new Department()
                                                    {
                                                        Id = 2,
                                                        Employees = new List<Employee>(),
                                                        Manager = new Employee() {FirstName = "Bob"}
                                                    }
                               };

            var magic = new DynamicAccessorCache();

            var path = Assign<Employee, string>(e => e.FirstName);
            Assert.AreEqual("FirstName", path);
            object actual = magic.Read(employee, path);
            Assert.AreEqual("Billy", actual);

            magic.Write(employee, path, "Ted");
            actual = magic.Read(employee, path);
            Assert.AreEqual("Ted", actual);
            
            path = Assign<Employee, long>(e => e.Department.Id);
            Assert.AreEqual("Department.Id", path);
            actual = magic.Read(employee, path);
            Assert.AreEqual(2L, employee.Department.Id);

            magic.Write(employee, path, 5);
            actual = magic.Read(employee, path);
            Assert.AreEqual(5L, actual);

            path = Assign<Employee, string>(e => e.Department.Manager.FirstName);
            Assert.AreEqual("Department.Manager.FirstName", path);
            actual = magic.Read(employee, path);
            Assert.AreEqual("Bob", actual);

            magic.Write(employee, path, "Hefferd");
            actual = magic.Read(employee, path);
            Assert.AreEqual("Hefferd", actual);
        }


        public string Assign<TClass, TProperty>(Expression<System.Func<TClass, TProperty>> property)
        {
            var expression = property.Body as MemberExpression;

            return DelimitedBuilder.Construct(GetPropertyPath(expression), ".");
        }

        public IEnumerable<string> GetPropertyPath(MemberExpression memberExpression)
        {
            if(memberExpression.Expression != null && memberExpression.Expression is MemberExpression)
            {
                foreach (var s in GetPropertyPath(memberExpression.Expression as MemberExpression))
                {
                    yield return s;
                }
            }
            yield return memberExpression.Member.Name;
        }
        Person myTest = new Person();

        [TestMethod]
        public void Test_100_Thousand_Writes_Reflector()
        {
            for (int i = 0; i < 100000; i++)
            {
                Reflector.Write(myTest, "FirstName", "test");
            }
        }

        [TestMethod]
        public void Test_100_Thousand_Writes_DynamicMethod()
        {
            var setter = NI.DynamicMethodFactory.CreateSetter(Reflector.GetPropertyInfo(typeof (Person), "FirstName", false));

            for (int i = 0; i < 100000; i++)
            {
                setter(myTest, "test");
            }
        }

        [TestMethod]
        public void Test_100_Thousand_Writes()
        {
            for (int i = 0; i < 100000; i++)
            {
                myTest.FirstName = "test";
            }
        }

        [TestMethod]
        public void Test_100_Thousand_Reads_Reflector()
        {
            for (int i = 0; i < 100000; i++)
            {
                var val = Reflector.Read(myTest, "FirstName");
            }
        }

        [TestMethod]
        public void Test_100_Thousand_Reads_DynamicMethod()
        {
            var getter = NI.DynamicMethodFactory.CreateGetter(Reflector.GetPropertyInfo(typeof(Person), "FirstName", false));

            for (int i = 0; i < 100000; i++)
            {
                var val = getter(myTest);
            }
        }

        [TestMethod]
        public void Test_100_Thousand_Reads()
        {
            for (int i = 0; i < 100000; i++)
            {
                var val = myTest.FirstName;
            }
        }
    }



    class A {}
    class B : A {}
    class C : B {}
    class D : C {}
    class E : D {}
    class F : E {}
    class G : F {}


    public class test : PersistableBase
    {
        internal string stringamajig = "";

        internal string nullString;

        internal Dictionary<string, int> testDictionary;

        internal List<float> testList;

        internal string[] stringArray = new[]
                                            {
                                                "a", "b", "c"
                                            };

        internal Queue<string> queue = new Queue<string>(new[] {"a", "b", "c"});

        internal DateTime date = DateTime.Now;

        private static string _field = "";

        public static string Field { get { return _field; } }
    }
}